package com.google.code.jholidays.io.xml;

import java.util.ArrayList;
import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.google.code.jholidays.core.DescriptorCollection;
import com.google.code.jholidays.core.EventDescriptor;

/**
 * Represents SAX parser which handles xml representation of
 * {@link DescriptorCollection}.
 * <p>
 * Implementation is based on state-machine
 * 
 * @author tillias
 * 
 */
public class SaxParser extends DefaultHandler {

    /**
     * Creates new instance of parser using given {@link IParserDirector}
     * 
     * @param director
     */
    public SaxParser(IParserDirector director) {
	this.director = director;
    }

    @Override
    public void startDocument() throws SAXException {
	setState(ParserState.Unknown);
    }

    @Override
    public void startElement(String uri, String localName, String qName,
	    Attributes attributes) throws SAXException {
	super.startElement(uri, localName, qName, attributes);

	if (getState() != ParserState.Error) {
	    ElementName elementName = getElementName(localName);

	    switch (elementName) {
	    case unknown:
		setState(ParserState.Error);
		break;
	    case descriptors:
		setState(ParserState.Init);
		descriptorsCollection = new ArrayList<EventDescriptor>();
		break;
	    case descriptor:
		if (getState() == ParserState.Init
			|| getState() == ParserState.EndRecord)
		    setState(ParserState.BeginRecord);
		else
		    setState(ParserState.Error);
		break;
	    case id:
		if (getState() == ParserState.BeginRecord)
		    setState(ParserState.BeginReadID);
		else
		    setState(ParserState.Error);
		break;
	    case name:
		if (getState() == ParserState.EndReadID)
		    setState(ParserState.BeginReadName);
		else
		    setState(ParserState.Error);
		break;
	    case description:
		if (getState() == ParserState.EndReadName)
		    setState(ParserState.BeginReadDescription);
		else
		    setState(ParserState.Error);
		break;
	    case parent_id:
		if (getState() == ParserState.EndReadDescription
			|| getState() == ParserState.EndReadName)
		    setState(ParserState.BeginReadParentID);
		else
		    setState(ParserState.Error);
		break;
	    case expression:
		if (getState() == ParserState.EndReadDescription
			|| getState() == ParserState.EndReadParentID
			|| getState() == ParserState.EndReadName)
		    setState(ParserState.BeginReadExpression);
		else
		    setState(ParserState.Error);
		break;
	    }
	}
    }

    @Override
    public void characters(char[] ch, int start, int length)
	    throws SAXException {

	if (getState() != ParserState.Error) {
	    try {
		String elementValue = new String(ch, start, length);

		switch (getState()) {
		case BeginReadID:
		    id = Integer.valueOf(elementValue);
		    break;
		case BeginReadName:
		    name = elementValue;
		    break;
		case BeginReadDescription:
		    description = elementValue;
		    break;
		case BeginReadParentID:
		    if (elementValue.equals(""))
			parent_id = 0;
		    else
			parent_id = Integer.valueOf(elementValue);
		    break;
		case BeginReadExpression:
		    expression = elementValue;
		    break;
		default:
		    break;
		}
	    } catch (Exception e) {
		setState(ParserState.Error);
	    }
	}

    }

    @Override
    public void endElement(String uri, String localName, String qName)
	    throws SAXException {
	super.endElement(uri, localName, qName);

	if (getState() != ParserState.Error) {
	    ElementName elementName = getElementName(localName);

	    switch (elementName) {
	    case unknown:
		setState(ParserState.Error);
		break;
	    case descriptors:
		setState(ParserState.End);
		finishedParsing();
		break;
	    case descriptor:
		if (getState() == ParserState.Init
			|| getState() == ParserState.EndReadExpression) {
		    setState(ParserState.EndRecord);
		    createDescriptor();
		} else
		    setState(ParserState.Error);
		break;
	    case id:
		if (getState() == ParserState.BeginReadID)
		    setState(ParserState.EndReadID);
		else
		    setState(ParserState.Error);
		break;
	    case name:
		if (getState() == ParserState.BeginReadName)
		    setState(ParserState.EndReadName);
		else
		    setState(ParserState.Error);
		break;
	    case description:
		if (getState() == ParserState.BeginReadDescription)
		    setState(ParserState.EndReadDescription);
		else
		    setState(ParserState.Error);
		break;
	    case parent_id:
		if (getState() == ParserState.BeginReadParentID)
		    setState(ParserState.EndReadParentID);
		else
		    setState(ParserState.Error);
		break;
	    case expression:
		if (getState() == ParserState.BeginReadExpression)
		    setState(ParserState.EndReadExpression);
		else
		    setState(ParserState.Error);
		break;
	    }
	}
    }

    private void finishedParsing() {
	if (director != null)
	    director.collectionParsed(descriptorsCollection);
    }

    private void createDescriptor() {
	EventDescriptor descriptor = new EventDescriptor(id, parent_id);
	descriptor.setName(name);
	descriptor.setDescription(description);
	descriptor.setExpression(expression);

	descriptorsCollection.add(descriptor);
    }

    protected ElementName getElementName(String strName) {
	ElementName result = ElementName.unknown;

	try {
	    result = ElementName.valueOf(strName);
	} catch (Exception e) {
	    result = ElementName.unknown;
	}

	return result;
    }

    protected void setState(ParserState newState) {
	// System.out.println("Old state" + this.state);
	this.state = newState;
	// System.out.println("New state" + newState);
    }

    protected ParserState getState() {
	return state;
    }

    ParserState state;
    List<EventDescriptor> descriptorsCollection;
    IParserDirector director;

    int id;
    String name;
    String description;
    int parent_id;
    String expression;
}

enum ElementName {
    descriptors, descriptor, id, name, description, parent_id, expression, unknown
}

enum ParserState {
    Error, Unknown, Init, BeginRecord, BeginReadID, BeginReadName, BeginReadDescription, BeginReadParentID, BeginReadExpression, EndReadID, EndReadName, EndReadDescription, EndReadParentID, EndReadExpression, EndRecord, End
}
